/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
    ConfigTypeInfer,
    D,
    identName,
    isDefined,
    toIDLFile,
} from "@idlizer/core";
import * as idl from '@idlizer/core/idl'
import * as ts from "typescript"
import * as path from "path"

const T = {
    stringArray: () => D.array(D.string())
}

export const IDLVisitorConfigurationSchema = D.object({
    DeletedDeclarations: T.stringArray(),
    DeletedMethods: D.map(D.string(), T.stringArray()),
    StubbedDeclarations: T.stringArray(),
    NameReplacements: D.map(D.string(), D.array(D.tuple(D.string(), D.string()))),
    ForceDefaultExport: D.default(D.map(D.string(), D.string()), new Map()),
    TypeReplacementsFilePath: D.string(),
})
export type IDLVisitorConfigurationSchemaType = ConfigTypeInfer<typeof IDLVisitorConfigurationSchema>
export type IDLVisitorConfiguration = IDLVisitorConfigurationSchemaType & IDLVisitorConfigurationHelpers
export interface IDLVisitorConfigurationHelpers {
    isDeletedDeclaration(name: string): boolean
    isStubbedDeclaration(name: string): boolean
    getReplacedDeclaration(name: string): idl.IDLEntry | undefined
    checkPropertyTypeReplacement(property: ts.PropertyDeclaration | ts.PropertySignature): [idl.IDLType?, idl.IDLEntry?]
    checkParameterTypeReplacement(parameter: ts.ParameterDeclaration): [idl.IDLType?, idl.IDLEntry?]
    checkTypedefReplacement(typedef: ts.TypeAliasDeclaration): [idl.IDLType?, idl.IDLEntry?]
    checkNameReplacement(name: string, file: ts.SourceFile): string
    parsePredefinedIDLFiles(pathBase: string): void

    TypeReplacementsFile: idl.IDLFile,
    ReplacedDeclarations: Map<string, idl.IDLEntry>

    checkMethodSignatureReplacement(methods: (ts.MethodDeclaration | ts.MethodSignature)[]): [idl.IDLMethod[]?, idl.IDLEntry[]?]
}

export function groupOverloadsTS(methods: (ts.MethodDeclaration | ts.MethodSignature)[]): (ts.MethodDeclaration | ts.MethodSignature)[][] {
    const groups = new Map<string, (ts.MethodDeclaration | ts.MethodSignature)[]>()
    for (const method of methods) {
        const methodName = identName(method.name)!
        if (!groups.has(methodName)) {
            groups.set(methodName, [])
        }
        const bucket = groups.get(methodName)
        bucket?.push(method)
    }
    return Array.from(groups.values())
}

export function expandIDLVisitorConfig(data:IDLVisitorConfigurationSchemaType): IDLVisitorConfiguration {
    return {
        ...data,
        isDeletedDeclaration(name: string) {
            return this.DeletedDeclarations.includes(name)
        },
        isStubbedDeclaration(name: string) {
            return this.StubbedDeclarations.includes(name)
        },
        getReplacedDeclaration(name: string) {
            return this.ReplacedDeclarations.get(name)
        },
        checkPropertyTypeReplacement(property: ts.PropertyDeclaration | ts.PropertySignature): [idl.IDLType?, idl.IDLEntry?] {
            const parent = property.parent
            if (!ts.isClassDeclaration(parent) && !ts.isInterfaceDeclaration(parent)) return []
            const className = identName(parent.name)!
            const propertyName = identName(property.name)!
            const entries: idl.IDLInterface[] = this.TypeReplacementsFile.entries.filter((it: idl.IDLEntry) => idl.isInterface(it)) as idl.IDLInterface[]
            const result = entries.find(it => it.name === className)?.properties.find((it: idl.IDLProperty) => it.name == propertyName)
            if (result) {
                let syntheticEntry: idl.IDLEntry | undefined
                if (idl.isReferenceType(result.type)) {
                    syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, result.type.name)
                }
                console.log(`Replaced type for ${className}.${propertyName}`)
                return [result.type, syntheticEntry]
            }
            return []
        },
        checkParameterTypeReplacement(parameter: ts.ParameterDeclaration): [idl.IDLType?, idl.IDLEntry?] {
            const method = parameter.parent
            if (!ts.isClassDeclaration(method.parent) && !ts.isInterfaceDeclaration(method.parent)) return []
            const parameterName = identName(parameter.name)!
            const methodName = identName(method.name)!
            const classOrInterfaceName = identName(method.parent.name)!
            const entries: idl.IDLInterface[] = this.TypeReplacementsFile.entries.filter((it: idl.IDLEntry) => idl.isInterface(it)) as idl.IDLInterface[]
            const result = entries.find(it => it.name === classOrInterfaceName)?.methods.find((it: idl.IDLMethod) => it.name == methodName)?.parameters.find((it: idl.IDLParameter) => it.name == parameterName)
            if (result) {
                let syntheticEntry: idl.IDLEntry | undefined
                if (idl.isReferenceType(result.type)) {
                    syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, result.type.name)
                }
                console.log(`Replaced type for ${classOrInterfaceName}.${methodName}(...${parameterName}...)`)
                return [idl.maybeOptional(result.type, result.isOptional), syntheticEntry]
            }
            return []
        },
        checkMethodSignatureReplacement(overloadedGroup: (ts.MethodDeclaration | ts.MethodSignature)[]): [idl.IDLMethod[]?, idl.IDLEntry[]?] {
            const rootMethod = overloadedGroup[0]
            if (!ts.isClassDeclaration(rootMethod.parent) && !ts.isInterfaceDeclaration(rootMethod.parent)) return []

            const classOrInterfaceName = identName(rootMethod.parent.name)!
            const methodName = identName(rootMethod.name)!
            const entries: idl.IDLInterface[] = this.TypeReplacementsFile.entries.filter((it: idl.IDLEntry) => idl.isInterface(it)) as idl.IDLInterface[]
            const result = entries.find(it => it.name === classOrInterfaceName)?.methods.filter((it: idl.IDLMethod) => it.name == methodName)
            if (!result || !result.length) return []

            let syntheticEntries: idl.IDLEntry[] = []
            for (let idlMethod of result) {
                if (idl.isReferenceType(idlMethod.returnType)) {
                    const syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, idlMethod.returnType.name)
                    if (syntheticEntry) syntheticEntries.push(syntheticEntry)
                }
                idlMethod.parameters.forEach(param => {
                    if (idl.isReferenceType(param.type)) {
                        const syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, param.type.name)
                        if (syntheticEntry) syntheticEntries.push(syntheticEntry)
                    }
                })
            }

            console.log(`Replaced signature for ${classOrInterfaceName}.${methodName}(...)`)
            return [result, syntheticEntries.length ? syntheticEntries : undefined]
        },
        checkTypedefReplacement(typedef: ts.TypeAliasDeclaration): [idl.IDLType?, idl.IDLEntry?] {
            const typename = identName(typedef.name)!
            let entries: idl.IDLTypedef[] = this.TypeReplacementsFile.entries.filter(it => idl.isTypedef(it)) as idl.IDLTypedef[]
            const result = entries.find(it => it.name == typename)
            if (result) {
                let syntheticEntry: idl.IDLEntry | undefined
                if (idl.isReferenceType(result.type)) {
                    syntheticEntry = findSyntheticDeclaration(this.TypeReplacementsFile, result.type.name)
                }
                if (idl.isUnionType(result.type)) {
                    result.type.name = result.name
                }
                console.log(`Replaced type for typedef ${typename}`)
                return [result.type, syntheticEntry]
            }
            return []
        },
        checkNameReplacement(name: string, file: ts.SourceFile): string {
            const filename: string = path.basename(file.fileName)
            const replacementPair = this.NameReplacements.get(filename)
            if (replacementPair) {
                if (replacementPair[0][0] === name) {
                    console.log(`Replaced "${name}" with "${replacementPair[1]}" in ${filename}`)
                    return replacementPair[0][1]
                }
            }
            return name
        },
        parsePredefinedIDLFiles(pathBase: string) {
            const typeReplacementsFile = toIDLFile(path.resolve(path.join(pathBase, this.TypeReplacementsFilePath)))
            if (typeReplacementsFile) {
                this.TypeReplacementsFile = typeReplacementsFile[0]
            }
        },

        TypeReplacementsFile: idl.createFile([]),
        ReplacedDeclarations: new Map<string, idl.IDLEntry>([
            ["CustomBuilder", idl.createCallback("CustomBuilder", [], idl.IDLVoidType)],
        ]),
    }
}

function findSyntheticDeclaration(file: idl.IDLFile, declName: string): idl.IDLEntry | undefined {
    return file.entries.filter(it => idl.isSyntheticEntry(it)).find(it => it.name == declName)
}
